﻿#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QDebug>
#include <QKeyEvent>
#include <QMenu>
#include <QtMath>
#include "plc_graphics_scene.h"

PlcGraphicsScene::PlcGraphicsScene(QGraphicsView* view, QObject* parent)
    : QGraphicsScene(parent)
    , mode_(NORMAL)
    , typeToCreate_(PlcInstructionType::NORMALLYOPEN)
    , view_(view)
    , gridSize_(83, 53)
{
    Q_ASSERT(view_);
}

// 重建每行的锚点
void PlcGraphicsScene::rebuildRowAnchors()
{
    // 清除旧锚点
    for (auto* it : rowAnchors_)
    {
        if (it)
        {
            removeItem(it);
            delete it;
        }
    }
    rowAnchors_.clear();

    const QRectF r = sceneRect();
    const double h = gridSize_.height();
    const int rows = qCeil((r.height()) / h) + 1;

    for (int i = 0; i < rows; ++i)
    {
        const double cy = i * h + h * 0.5; // 每行中心
        // 让圆心恰在 x=0
        QRectF er(3.0 - kAnchorRadius, cy - kAnchorRadius, kAnchorRadius * 2.0, kAnchorRadius * 2.0);
        auto* dot = new QGraphicsEllipseItem(er);
        dot->setBrush(Qt::black);
        dot->setPen(Qt::NoPen);
        dot->setZValue(0.5);                  // 比背景略高
        dot->setData(0, QStringLiteral("anchor")); // 识别标记
        addItem(dot);

        rowAnchors_.push_back(dot);
    }
}

void PlcGraphicsScene::clearAll()
{
    // 统一清空场景内所有图元
    QGraphicsScene::clear();

    // 不去访问/删除 rowAnchors_ 里旧指针（它们已无效）
    // 直接清空指针数组，避免 rebuildRowAnchors() 里二次 delete
    rowAnchors_.clear();
}

// 网格中心吸附
QPointF PlcGraphicsScene::cellCenterAt(const QPointF& scenePos) const
{
    double w = gridSize_.width();
    double h = gridSize_.height();

    double ox = gridOriginX_;      // 与 drawBackground 的 left+33 对齐
    double oy = 0.0;                // 如需纵向也偏移，可加 gridOriginY_
    double col = qRound((scenePos.x() - ox) / w);
    double row = qRound((scenePos.y() - oy) / h);
    return QPointF(ox + col * w + w * 0.5, oy + row * h + h * 0.5);
}

QPointF PlcGraphicsScene::snapTopLeftToCellCenter(const QPointF& scenePos, const QSizeF& item) const
{
    QPointF center = cellCenterAt(scenePos);
    return center - QPointF(item.width() * 0.5, item.height() * 0.5); // 把“中心”转换为“左上角”
}

// 复制：保存类型、尺寸、ON状态
void PlcGraphicsScene::copyFrom(const PlcElement* src)
{
    if (!src) return;
    clip_.valid = true;
    clip_.type  = src->instructionType();
    clip_.size  = QSizeF(src->boundingRect().width(), src->boundingRect().height());
    clip_.on    = src->isOn();

    // 拷贝属性对话框设置的值
    clip_.prefix = src->propPrefix();
    clip_.v1     = src->propValue();
    clip_.v2     = src->propValue2();
}

// 是否可粘贴
bool PlcGraphicsScene::canPaste() const
{
    return clip_.valid;
}

// 在指定位置粘贴一个新元件（落在网格中心/左上吸附）
PlcElement* PlcGraphicsScene::pasteAt(const QPointF& scenePos)
{
    if (!clip_.valid) return nullptr;

    // 粘贴前检查该网格是否已被占用
    if (isOccupied(scenePos)) return nullptr;


    auto *item = new PlcElement();
    item->setInstructionType(clip_.type);
    item->setSize(clip_.size.width(), clip_.size.height());
    item->setOn(clip_.on);

    // 还原标题对话框的值
    item->setPropPrefix(clip_.prefix);
    item->setPropValue(clip_.v1);
    item->setPropValue2(clip_.v2);

    const QPointF topLeft = snapTopLeftToCellCenter(scenePos, clip_.size);
    item->setPos(topLeft);
    addItem(item);

    emit elementCreated(item);
    return item;
}

bool PlcGraphicsScene::insertConnection(PlcElement* element)
{
    if (!element) return false;

    // 以元件中心做投影判断
    const QPointF center = element->mapToScene(QPointF(element->boundingRect().width()/2.0,
                                                       element->boundingRect().height()/2.0));

    // 允许一定容差
    constexpr double kHitTol = 10.0;

    auto isHorizontal = [](const QPointF& a, const QPointF& b) {
        return std::abs(a.y() - b.y()) < 0.5;
    };

    // 查找所有连接项
    double bestDist = std::numeric_limits<double>::max();
    QPointF bestProj;
    ConnectLine* bestConn = nullptr;

    for (QGraphicsItem* item : items())
    {
        ConnectLine* conn = qgraphicsitem_cast<ConnectLine*>(item);
        if (!conn) continue;

        // 跳过非水平连接线
        if (!isHorizontal(conn->line().p1(), conn->line().p2())) continue;

        // 对于折线，需要检查水平段
        if (conn->isPolyline())
        {
            const QPointF p1 = conn->line().p1();
            const QPointF p2 = conn->line().p2();
            const QPointF bend = conn->bendPoint();

            // 检查水平段（p1到bend或bend到p2）
            QLineF horizontalSeg1(p1, QPointF(bend.x(), p1.y()));
            QLineF horizontalSeg2(QPointF(bend.x(), p2.y()), p2);

            if (isHorizontal(horizontalSeg1.p1(), horizontalSeg1.p2()))
            {
                const double x1 = std::min(horizontalSeg1.x1(), horizontalSeg1.x2());
                const double x2 = std::max(horizontalSeg1.x1(), horizontalSeg1.x2());
                const double y = horizontalSeg1.y1();

                QPointF proj(qBound(x1, center.x(), x2), y);
                const double dist = QLineF(center, proj).length();

                if (dist < bestDist && dist <= kHitTol)
                {
                    bestDist = dist;
                    bestProj = proj;
                    bestConn = conn;
                }
            }

            if (isHorizontal(horizontalSeg2.p1(), horizontalSeg2.p2()))
            {
                const double x1 = std::min(horizontalSeg2.x1(), horizontalSeg2.x2());
                const double x2 = std::max(horizontalSeg2.x1(), horizontalSeg2.x2());
                const double y = horizontalSeg2.y1();

                QPointF proj(qBound(x1, center.x(), x2), y);
                const double dist = QLineF(center, proj).length();

                if (dist < bestDist && dist <= kHitTol)
                {
                    bestDist = dist;
                    bestProj = proj;
                    bestConn = conn;
                }
            }
        }
        else
        {
            // 对于直线，直接检查
            const QPointF p1 = conn->line().p1();
            const QPointF p2 = conn->line().p2();

            if (isHorizontal(p1, p2))
            {
                const double x1 = std::min(p1.x(), p2.x());
                const double x2 = std::max(p1.x(), p2.x());
                const double y = p1.y();

                QPointF proj(qBound(x1, center.x(), x2), y);
                const double dist = QLineF(center, proj).length();

                if (dist < bestDist && dist <= kHitTol)
                {
                    bestDist = dist;
                    bestProj = proj;
                    bestConn = conn;
                }
            }
        }
    }

    if (!bestConn) return false; // 未命中水平线段

    // 创建新的连接线，但不修改原有的折线
    ConnectLine* newConn1 = nullptr;
    ConnectLine* newConn2 = nullptr;

    // 获取原有连接的起点和终点信息
    PlcElement* startItem = bestConn->startItem();
    PlcElement* endItem = bestConn->endItem();
    ConnectLine::Side startSide = bestConn->startSide();
    ConnectLine::Side endSide = bestConn->endSide();

    bool startIsAnchor = bestConn->startIsAnchor();
    bool endIsAnchor = bestConn->endIsAnchor();
    QPointF startAnchorPos = bestConn->startAnchorScenePos();
    QPointF endAnchorPos = bestConn->endAnchorScenePos();

    // 创建从原有起点到新元件的连接
    if (startIsAnchor)
    {
        newConn1 = new ConnectLine(element, ConnectLine::LEFT, startAnchorPos);
    }
    else if (startItem)
    {
        newConn1 = new ConnectLine(startItem, startSide, element, ConnectLine::LEFT);
    }

    // 创建从新元件到原有终点的连接
    if (endIsAnchor)
    {
        newConn2 = new ConnectLine(element, ConnectLine::RIGHT, endAnchorPos);
    }
    else if (endItem)
    {
        newConn2 = new ConnectLine(element, ConnectLine::RIGHT, endItem, endSide);
    }

    // 添加到场景和元件
    if (newConn1)
    {
        addItem(newConn1);
        if (startItem) startItem->addConnection(newConn1);
        element->addConnection(newConn1);
        newConn1->updatePosition();
    }

    if (newConn2)
    {
        addItem(newConn2);
        element->addConnection(newConn2);
        if (endItem) endItem->addConnection(newConn2);
        newConn2->updatePosition();
    }

    // 移除原有连接
    if (startItem)
    {
        startItem->removeConnection(bestConn);
    }
    if (endItem)
    {
        endItem->removeConnection(bestConn);
    }
    removeItem(bestConn);
    delete bestConn;

    return true;
}

bool PlcGraphicsScene::isOccupied(const QPointF &scenePos, const PlcElement *ignore) const
{
    // 定位到该点所属网格格子的“中心”
    const QPointF center = cellCenterAt(scenePos);

    // 构造该格子的矩形（以网格大小为边界）
    const QRectF cellRect(center.x() - gridSize_.width()  * 0.5,
                          center.y() - gridSize_.height() * 0.5,
                          gridSize_.width(),
                          gridSize_.height());

    // 查找落在该格子范围内的场景项（用边界相交即可）
    const QList<QGraphicsItem*> hit = items(cellRect, Qt::IntersectsItemBoundingRect);
    for (QGraphicsItem* it : hit)
    {
        if (!it) continue;
        if (it->type() == PlcElement::Type && it != ignore)
            return true; // 已被其它元件占用
    }
    return false;
}

double PlcGraphicsScene::snapXToGrid(double x) const
{
    double w = gridSize_.width();
    double ox = gridOriginX_;
    // 计算最近的网格中心x坐标
    double col = qRound((x - ox) / w);
    return ox + col * w;
}

bool PlcGraphicsScene::checkIfInsertionNeeded(PlcElement* element) const
{
    if (!element) return false;

    // 以元件中心做投影判断
    const QPointF center = element->mapToScene(QPointF(element->boundingRect().width()/2.0,
                                                       element->boundingRect().height()/2.0));

    constexpr double kHitTol = 10.0;

    auto isHorizontal = [](const QPointF& a, const QPointF& b) {
        return std::abs(a.y() - b.y()) < 0.5;
    };

    // 检查是否有水平线段在容差范围内
    for (QGraphicsItem* item : items())
    {
        ConnectLine* conn = qgraphicsitem_cast<ConnectLine*>(item);
        if (!conn) continue;

        if (conn->isPolyline())
        {
            const QPointF p1 = conn->line().p1();
            const QPointF p2 = conn->line().p2();
            const QPointF bend = conn->bendPoint();

            // 检查水平段
            if (isHorizontal(p1, bend))
            {
                const double x1 = std::min(p1.x(), bend.x());
                const double x2 = std::max(p1.x(), bend.x());
                const double y = p1.y();

                if (center.y() >= y - kHitTol && center.y() <= y + kHitTol &&
                    center.x() >= x1 && center.x() <= x2)
                {
                    return true;
                }
            }

            if (isHorizontal(bend, p2))
            {
                const double x1 = std::min(bend.x(), p2.x());
                const double x2 = std::max(bend.x(), p2.x());
                const double y = bend.y();

                if (center.y() >= y - kHitTol && center.y() <= y + kHitTol &&
                    center.x() >= x1 && center.x() <= x2)
                {
                    return true;
                }
            }
        }
        else if (isHorizontal(conn->line().p1(), conn->line().p2()))
        {
            const QPointF p1 = conn->line().p1();
            const QPointF p2 = conn->line().p2();
            const double x1 = std::min(p1.x(), p2.x());
            const double x2 = std::max(p1.x(), p2.x());
            const double y = p1.y();

            if (center.y() >= y - kHitTol && center.y() <= y + kHitTol &&
                center.x() >= x1 && center.x() <= x2)
            {
                return true;
            }
        }
    }

    return false;
}

void PlcGraphicsScene::setMode(Mode mode)
{
    mode_ = mode;
}

void PlcGraphicsScene::setInstructionTypeToCreate(PlcInstructionType type)
{
    typeToCreate_ = type;
}

void PlcGraphicsScene::deleteSelectedItems()
{
    const auto sel = selectedItems();

    // 先删元件（它们会在析构中安全拆线）
    for (QGraphicsItem* it : sel)
    {
        if (it->type() == PlcElement::Type)
        {
            auto* elem = static_cast<PlcElement*>(it);
            elem->removeConnections();         // 可留作显式拆线
            removeItem(elem);
            delete elem;
        }
    }

    // 再删仍然存在的连线（带 scene() 校验）
    for (QGraphicsItem* it : sel)
    {
        if (it->type() == ConnectLine::Type)
        {
            auto* conn = static_cast<ConnectLine*>(it);
            if (!conn || conn->scene() != this) continue;  // 防止二次删除
            if (!conn->startIsAnchor() && conn->startItem()) conn->startItem()->removeConnection(conn);
            if (!conn->endIsAnchor() && conn->endItem())   conn->endItem()->removeConnection(conn);
            removeItem(conn);
            delete conn;
        }
    }
    emit sceneContentChanged();
}

void PlcGraphicsScene::startConnectionDrag(PlcElement* element, bool fromRight)
{
    dragElement_ = element;
    dragFromRight_ = fromRight;

    // 起点改为“真实端点中心”，保证拖拽线从黑点出发
    QPointF p0 = fromRight ? element->mapToScene(element->rightConnectionPoint())
                           : element->mapToScene(element->leftConnectionPoint());
    tempLine_ = new QGraphicsLineItem(QLineF(p0, p0));
    tempLine_->setPen(QPen(Qt::black, 2));
    addItem(tempLine_);
}

void PlcGraphicsScene::updateConnectionDrag(const QPointF& scenePos)
{
    if (tempLine_ && dragElement_)
    {
        QLineF newLine = tempLine_->line();
        newLine.setP2(scenePos);
        tempLine_->setLine(newLine);
    }
}

void PlcGraphicsScene::endConnectionDrag(const QPointF& scenePos)
{
    if (!tempLine_ || !dragElement_) return;

    PlcElement* endElement = nullptr;
    bool endIsLeft = false;
    QPointF hitAnchorCenter;
    bool hitAnchor = false;

    const QList<QGraphicsItem*> under = items(scenePos);

    // 1、优先：命中元件端点
    for (QGraphicsItem* it : under)
    {
        if (it->type() == PlcElement::Type)
        {
            auto* elem = qgraphicsitem_cast<PlcElement*>(it);
            const QPointF localPos = elem->mapFromScene(scenePos);
            bool isLeft = false;
            if (elem->isConnectionPointHit(localPos, isLeft))
            {
                endElement = elem;
                endIsLeft = isLeft;
                break;
            }
        }
    }

    // 2、其次：命中行锚点
    if (!endElement)
    {
        for (QGraphicsItem* it : under)
        {
            if (it->data(0).toString() == QStringLiteral("anchor"))
            {
                hitAnchorCenter = it->sceneBoundingRect().center();
                hitAnchor = true;
                break;
            }
        }
    }

    // 3、命中“线段”（ConnectLine）
    ConnectLine* hitLine = nullptr;
    QPointF projected;
    if (!endElement && !hitAnchor)
    {
        for (QGraphicsItem* it : under)
        {
            if (it->type() == ConnectLine::Type)
            {
                hitLine = qgraphicsitem_cast<ConnectLine*>(it);
                if (hitLine) {
                    // 计算 scenePos 到该线段的投影点
                    const QLineF L = hitLine->line();
                    const QPointF A = L.p1();
                    const QPointF B = L.p2();
                    const QPointF AP = scenePos - A;
                    const QPointF AB = B - A;
                    const double ab2 = AB.x()*AB.x() + AB.y()*AB.y();
                    double t = 0.0;
                    if (ab2 > 0.0)
                    {
                        t = (AP.x()*AB.x() + AP.y()*AB.y()) / ab2;
                        t = std::clamp<double>(t, 0.0, 1.0);
                    }
                    projected = A + t * (B - A);
                }
                break;
            }
        }
    }

    // 建立连接
    if (endElement && endElement != dragElement_)
    {
        ConnectLine::Side startSide = dragFromRight_ ? ConnectLine::RIGHT : ConnectLine::LEFT;
        ConnectLine::Side endSide   = endIsLeft ? ConnectLine::LEFT   : ConnectLine::RIGHT;

        auto* conn = new ConnectLine(dragElement_, startSide, endElement, endSide);
        addItem(conn);
        dragElement_->addConnection(conn);
        endElement->addConnection(conn);
        conn->updatePosition();
    }
    else if (hitAnchor)
    {
        ConnectLine::Side startSide = dragFromRight_ ? ConnectLine::RIGHT : ConnectLine::LEFT;
        auto* conn = new ConnectLine(dragElement_, startSide, hitAnchorCenter);
        addItem(conn);
        dragElement_->addConnection(conn);
        conn->updatePosition();
    }
    else if (hitLine)  // 命中线段
    {
        // 计算 scenePos 到该线段的投影点
        const QLineF L = hitLine->line();
        const QPointF A = L.p1();
        const QPointF B = L.p2();
        const QPointF AP = scenePos - A;
        const QPointF AB = B - A;
        const double ab2 = AB.x()*AB.x() + AB.y()* AB.y();
        double t = 0.0;
        if (ab2 > 0.0)
        {
            t = (AP.x()*AB.x() + AP.y()*AB.y()) / ab2;
            t = std::clamp<double>(t, 0.0, 1.0);
        }
        projected = A + t * (B - A);

        // 将投影点的x坐标吸附到网格垂直中心线
        projected.setX(snapXToGrid(projected.x()));

        // 创建连接线
        ConnectLine::Side startSide = dragFromRight_ ? ConnectLine::RIGHT : ConnectLine::LEFT;
        auto* conn = new ConnectLine(dragElement_, startSide, projected);
        addItem(conn);
        dragElement_->addConnection(conn);

        // 设为"折线显示"：规则采用"水平 -> 垂直"L 形
        conn->updatePosition();
        const QPointF s = conn->line().p1();
        const QPointF e = conn->line().p2();
        const QPointF bend(e.x(), s.y());
        conn->setPolylineMode(true, bend);
    }

    // 清理拖线
    removeItem(tempLine_);
    delete tempLine_;
    tempLine_ = nullptr;

    dragElement_ = nullptr;
    dragFromRight_ = false;
}

void PlcGraphicsScene::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace)
    {
        deleteSelectedItems();
        event->accept();
        return;
    }
    else if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier))
    {
        emit copyRequestFromScene();
        event->accept();
        return;
    }
    else if (event->key() == Qt::Key_V && (event->modifiers() & Qt::ControlModifier))
    {
        // 获取鼠标当前位置
        if (view_) {
            QPoint viewPos = view_->mapFromGlobal(QCursor::pos());
            QPointF scenePos = view_->mapToScene(viewPos);
            emit pasteRequestFromScene(scenePos);
        }
        event->accept();
        return;
    }

    QGraphicsScene::keyPressEvent(event);
}

void PlcGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
    // 命中检查：若点在元素上，则由元素自己的 contextMenuEvent 处理
    QGraphicsItem* hit = itemAt(event->scenePos(), QTransform());
    if (hit)
    {
        // 交由该 item 处理
        QGraphicsScene::contextMenuEvent(event);
        return;
    }

    // 空白处：只弹出“粘贴”
    QMenu menu;
    QAction *actPaste = menu.addAction(QStringLiteral("粘贴"));
    if (!canPaste())
    {
        actPaste->setEnabled(false);
    }

    QAction *chosen = menu.exec(event->screenPos());
    if (chosen == actPaste && canPaste())
    {
        pasteAt(event->scenePos());
        event->accept();
        return;
    }

    QGraphicsScene::contextMenuEvent(event);
}

// 建元件：落在网格中心
void PlcGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() == Qt::LeftButton && mode_ == CREATE)
    {
        // 如果点击的格子已被占用，则不创建，直接退出
        if (isOccupied(event->scenePos(), nullptr)) {
            setMode(NORMAL);
            event->accept();
            return;
        }

        auto *item = new PlcElement();
        item->setInstructionType(typeToCreate_);

        const QSizeF itemSize(item->boundingRect().width(), item->boundingRect().height());
        const QPointF placeTopLeft = snapTopLeftToCellCenter(event->scenePos(), itemSize);

        item->setPos(placeTopLeft);
        addItem(item);

        emit elementCreated(item);
        setMode(NORMAL);
        event->accept();
        return;
    }
    QGraphicsScene::mousePressEvent(event);
}
